package com.qd.pay.service.order;

import com.cdqidi.exception.ApiException;
import com.cdqidi.util.DateUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.qd.pay.config.PayConstant;
import com.qd.pay.config.PayInfoConfig;
import com.qd.pay.domain.*;
import com.qd.pay.model.*;
import com.qd.pay.service.*;
import com.qd.pay.thread.OrderNotifyThread;
import com.qd.pay.util.IdUtil;
import com.qd.tfpay.order.BaseResponseBO;
import com.qd.tfpay.order.OrderApi;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
 * @author sjk
 */
@Component
@RequiredArgsConstructor
@Accessors(chain = true)
@Slf4j
public class TfOrderService {

    private final OrderApi orderApi;
    private final OrderService orderService;
    private final OrderBodyService orderBodyService;
    private final OrderTfService orderTfService;
    private final ObjectMapper objectMapper;
    private final PayInfoConfig payInfoConfig;
    private final OrderRefundReportService orderRefundReportService;
    private final OrderReportService orderReportService;
    private final OrderRefundService orderRefundService;
    private final OrderReportNotifyService orderReportNotifyService;
    private final OrderExtendedService orderExtendedService;
    private final OrderNotifyThread orderNotifyThread;
    /**
     * 关闭失败
     */
    private static final String ERR_CLOSE_ORDER_STATE = "1";


    /**
     * 关闭订单
     *
     * @param order 订单信息
     */
    @SneakyThrows
    public void closeWxOrder(Order order) {
        OrderTf orderTf = Optional.ofNullable(orderTfService.getById(order.getOrderId())).orElseThrow(() -> new ApiException("天府银行订单不存在"));
        BaseResponseBO response = orderApi.closeOrder(orderTf.getPartner(), orderTf.getSubPartner(), order.getOutTradeNo());
        try {
            if (!response.isSuccess()) {
                throw new ApiException(response.getRetMsg());
            }
            JsonNode originalMessage = response.getOriginalMessage();
            String outTradeNo = Optional.ofNullable(originalMessage.get("out_trade_no")).orElseThrow(() -> new ApiException("商户订单号为空")).toString();
            if (outTradeNo.equals(order.getOutTradeNo())) {
                String resultMsg = "商户订单号不一致,请求商户订单号: " + order.getOutTradeNo() + ",返回商户订单号: " + outTradeNo;
                throw new ApiException(resultMsg);
            }
            String closeState = Optional.ofNullable(originalMessage.get("close_state")).orElseThrow(() -> new ApiException("订单关闭状态为空")).toString();
            if (closeState.equals(ERR_CLOSE_ORDER_STATE)) {
                throw new ApiException("订单关闭失败");
            }
            order.setState(1).setUpdateTime(LocalDateTime.now());
            orderService.updateById(order);

            OrderBody orderBody = new OrderBody().setBid(order.getOrderId()).setType(OrderBodyDTO.ORDER_TYPE_ORDER_CLOSE).setContent(response.getOriginalMessage().toPrettyString()).setAddTime(order.getUpdateTime());
            orderBodyService.save(orderBody);

            orderBody.freeData();
            order.freeData();
        } finally {
            response.freeData();
        }
    }

    /**
     * 创建订单
     *
     * @param dto   请求参数
     * @param order 订单信息
     * @param body  报文
     */
    @SneakyThrows
    public Map<String, String> createOrder(OrderDTO dto, Order order, OrderBody body) {
        Map<String, Object> reqParams = new HashMap<>(16);
        reqParams.put("partner", dto.getPartner());
        reqParams.put("subpartner", dto.getSubPartner());
        reqParams.put("out_trade_no", order.getOutTradeNo());
        reqParams.put("subject", order.getSubject());
        reqParams.put("body", order.getBody());
        reqParams.put("total_fee", order.getPayAmount());
        reqParams.put("fee_type", 1);
        reqParams.put("spbill_create_ip", order.getIpAddress());
        reqParams.put("attach", order.getOrderId());
        if(dto.getType().equals(PayConstant.ORDER_TYPE_2)){
            reqParams.put("trans_channel", "wx_lite");
        }else if(dto.getType().equals(PayConstant.ORDER_TYPE_3)){
            reqParams.put("trans_channel", "wx_pub");
        }
        reqParams.put("notify_url", payInfoConfig.getDomain() + "/gy-panda-park/sys/payOrder/tfPayNotify");
        reqParams.put("channel_details", dto.getChannelDetails());
        BaseResponseBO response = orderApi.createOrder(reqParams);
        if (!response.isSuccess()) {
            throw new ApiException("创建订单失败: " + response.getRetMsg());

        }
        JsonNode jsonNode = response.getOriginalMessage();
        int tradeState = Optional.ofNullable(jsonNode.get("trade_state")).orElseThrow(() -> new ApiException("交易状态为空")).intValue();
        if (tradeState != 0) {
            log.error("创建订单失败，交易状态:{}", tradeState);
            throw new ApiException("创建订单失败");
        }

        saveTfOrder(dto, order, jsonNode);
        body.setContent(objectMapper.writeValueAsString(response));

        String returnMsg = Optional.ofNullable(jsonNode.get("returnMsg")).orElseThrow(() -> new ApiException("returnMsg为空")).asText();
        if (!returnMsg.contains("appId") || returnMsg.lastIndexOf("\"}") == -1) {
            log.error("returnMsg: {},json: {}", returnMsg, jsonNode);
            throw new ApiException("创建订单异常,联系管理员");
        }
        returnMsg = returnMsg.substring(returnMsg.indexOf("appId"), returnMsg.lastIndexOf("\"}"));
        final String[] returnMsgList = returnMsg.split("&");
        Map<String, String> returnParams = new HashMap<>(16);
        for (String s : returnMsgList) {
            String key = s.substring(0, s.indexOf("="));
            String value = s.substring(key.length() + 1);
            returnParams.put(key, value);
        }
        returnParams.put("orderId", order.getOrderId());
        returnParams.put("thirdPartyOrder", order.getThirdPartyOrder());


        return returnParams;
    }

    /**
     * 退款
     *
     * @param dto         请求参数
     * @param order       订单信息
     * @param orderRefund 退款单
     * @param orderBody   报文
     */
    @SneakyThrows
    public void orderRefund(OrderRefundDTO dto, Order order, OrderRefund orderRefund, OrderBody orderBody) {
        OrderTf orderTf = Optional.ofNullable(orderTfService.getById(order.getOrderId())).orElseThrow(() -> new ApiException("天府银行订单不存在，不能完成退款"));

        String outRefundNo = IdUtil.getOutRefundNo32();

        Map<String, Object> reqParams = new HashMap<>(16);
        reqParams.put("partner", orderTf.getPartner());
        reqParams.put("subpartner", orderTf.getSubPartner());
        reqParams.put("out_trade_no", order.getOutTradeNo());
        reqParams.put("transaction_id", order.getTransactionId());
        reqParams.put("out_refund_no", outRefundNo);
        reqParams.put("total_fee", dto.getPayAmount());
        reqParams.put("refund_fee", dto.getRefundFee());
        reqParams.put("refund_reason", dto.getRefundDesc());
        reqParams.put("refund_notify_url", payInfoConfig.getDomain() + "/gy-panda-park/sys/payOrder/tfRefundNotify");

        BaseResponseBO responseBO = orderApi.refundOrder(reqParams);
        if (!responseBO.isSuccess()) {
            log.error("发起退款失败,商户号: {},子商户号: {},订单金额: {},退款金额: {},异常信息: {}", orderTf.getPartner(), orderTf.getSubPartner(), dto.getPayAmount(), dto.getRefundFee(), responseBO.getRetMsg());
            throw new ApiException("发起退款失败: " + responseBO.getRetMsg());
        }
        orderBody.setContent(objectMapper.writeValueAsString(reqParams));


        final JsonNode originalMessage = responseBO.getOriginalMessage();
        int refundStatus = originalMessage.get("refund_status").intValue();

        if (refundStatus == 1) {
            throw new ApiException("退款失败");
        }

        String transactionId = originalMessage.get("transaction_id").asText();
        String outTradeNo = originalMessage.get("out_trade_no").asText();
        String refundTotalFee = originalMessage.get("refund_total_fee").asText();
        BigDecimal refundFee = originalMessage.get("refund_fee").decimalValue();

        log.info("退款总金额:{},已退款金额:{}", refundFee.toPlainString(), refundTotalFee);
        if (refundStatus == 0) {
            //退款成功
            OrderRefundReport orderRefundReport = new OrderRefundReport();
            orderRefundReport.setRid(orderRefund.getRid())
                    .setTransactionId(transactionId)
                    .setOrderId(order.getOrderId())
                    .setRefundNo(orderRefund.getRefundNo())
                    .setOutRefundNo(outRefundNo)
                    .setOutTradeNo(outTradeNo)
                    .setTimeEnd(LocalDateTime.now())
                    .setRefundFee(new BigDecimal(refundTotalFee))
                    .setAddTime(LocalDateTime.now())
                    .setRefundStatus(OrderService.SUCCESS);
            orderRefundReportService.save(orderRefundReport);
            saveRefundReportNotify(dto.getRefundDesc(), orderRefundReport, order.getPayAmount());
        }
        //还有一个是等于3的，正在退款中的，通过异步通知接口处理
        orderRefund.setTransactionId(transactionId);
        orderRefund.setOutRefundNo(outRefundNo);
        orderRefund.setOutTradeNo(outTradeNo);
        orderRefund.setRefundFee(new BigDecimal(refundTotalFee));
    }

    /**
     * 支付回执消息
     */
    @SneakyThrows
    @Transactional(rollbackFor = Exception.class)
    public void payNotify(JsonNode jsonNode) {
        log.info("jsonNode: {}", jsonNode.toPrettyString());
        String orderId = Optional.ofNullable(jsonNode.get("attach")).orElseThrow(() -> new ApiException("商家数据包不能为空")).asText();
        Order order = orderService.getById(orderId);
        if (null == order) {
            log.info("订单不存在，不处理,orderId:{}", orderId);
            return;
        }
        OrderReportDTO dataDto = orderReportService.getByIdDto(orderId);
        String tradeState = Optional.ofNullable(jsonNode.get("trade_state")).orElseThrow(() -> new ApiException("支付结果为空")).asText();
        String totalFee = Optional.ofNullable(jsonNode.get("total_fee")).orElseThrow(() -> new ApiException("总金额为空")).asText();
        String timeEnd = Optional.ofNullable(jsonNode.get("time_end")).map(JsonNode::asText).orElse(null);
        String bankType = Optional.ofNullable(jsonNode.get("bank_type")).map(JsonNode::asText).orElse(null);
        if (null == dataDto) {
            String retCode = Optional.ofNullable(jsonNode.get("retcode")).orElseThrow(() -> new ApiException("返回码为空")).asText();
            if (retCode.equals("1")) {
                throw new ApiException("retCode状态为失败");
            }
            String transactionId = Optional.ofNullable(jsonNode.get("transaction_id")).orElseThrow(() -> new ApiException("支付流水号为空")).asText();
            log.info("orderId:{},transactionId:{},支付结果:{}", orderId, transactionId, tradeState);
            OrderReport data = new OrderReport();
            data.setOrderId(orderId)
                    .setTransactionId(transactionId)
                    .setPayAmount(new BigDecimal(totalFee))
                    .setAddTime(LocalDateTime.now());
            data.setResultCode(buildResultCode(tradeState));
            if (StringUtils.hasLength(timeEnd)) {
                data.setTimeEnd(DateUtil.strToLocalDateTime(timeEnd, "yyyyMMdd HH:mm:ss"));
            }
            if (StringUtils.hasLength(bankType)) {
                data.setBankType(bankType);
            }
            orderReportService.save(data);
            saveReportNotify(data);

            OrderBody orderBody = new OrderBody()
                    .setAddTime(data.getAddTime())
                    .setBid(data.getOrderId())
                    .setContent(jsonNode.toPrettyString())
                    .setType(OrderBodyDTO.ORDER_TYPE_ORDER_REPORT);
            orderBodyService.save(orderBody);

            orderBody.freeData();
            data.freeData();
        } else {
            final String resultCode = dataDto.getResultCode();
            final String newResultCode = buildResultCode(tradeState);
            if (resultCode.equals(newResultCode)) {
                //已经处理过的，状态一致的不处理
                return;
            }
            OrderReport data = new OrderReport();
            data.setOrderId(orderId);
            if (StringUtils.hasLength(timeEnd)) {
                data.setTimeEnd(DateUtil.strToLocalDateTime(timeEnd, "yyyyMMdd HH:mm:ss"));
            }
            if (StringUtils.hasLength(bankType)) {
                data.setBankType(bankType);
            }
            orderReportService.updateById(data);
            saveReportNotify(data);
            dataDto.freeData();
            data.freeData();
        }
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCompletion(int status) {
                if (TransactionSynchronization.STATUS_COMMITTED == status) {
                    log.info("提交成功，支付回执消息");
                    orderNotifyThread.awaken();
                }
            }
        });
    }

    /**
     * 0表示成功,1 交易失败, 2未支付,3 处理中,4支付成功等待买家收货 6-订单关闭
     *
     * @param tradeState 天府银行的状态标志
     * @return 成功 or 失败
     */
    private static String buildResultCode(String tradeState) {
        switch (tradeState) {
            case "0":
                return OrderService.SUCCESS;
            case "1":
                return OrderService.FAIL;
            case "3":
            case "4":
            case "6":
            default:
                return tradeState;
        }
    }

    /**
     * 退款入账通知
     */
    @SneakyThrows
    @Transactional(rollbackFor = Exception.class)
    public void refundNotify(ObjectNode jsonNodes) {
        String transactionId = Optional.ofNullable(jsonNodes.get("transaction_id")).orElseThrow(() -> new ApiException("支付流水号为空")).asText();
        String outRefundNo = Optional.ofNullable(jsonNodes.get("out_refund_no")).orElseThrow(() -> new ApiException("平台退款流水号为空")).asText();
        BigDecimal refundFee = Optional.ofNullable(jsonNodes.get("refund_fee")).orElseThrow(() -> new ApiException("平台退款流水号为空")).decimalValue();
        String refundStatus = Optional.ofNullable(jsonNodes.get("refund_status")).orElseThrow(() -> new ApiException("退款入账状态为空")).asText();
        JsonNode refundRecvAccout = jsonNodes.get("refund_recv_accout");
        JsonNode refundTime = jsonNodes.get("refund_time");

        OrderRefundDTO orderRefund = orderRefundService.getOne(transactionId, outRefundNo);
        if (null == orderRefund) {
            log.info("退款订单不存在,不处理,transactionId: {},outRefundNo: {}", transactionId, outRefundNo);
            return;
        }
        log.info("rid: {}", orderRefund.getRid());
        OrderRefundReport report = orderRefundReportService.getOne(transactionId, outRefundNo);
        if (null == report) {
            report = new OrderRefundReport();
            report.setRid(orderRefund.getRid())
                    .setOrderId(orderRefund.getOrderId())
                    .setOutRefundNo(outRefundNo)
                    .setRefundNo(orderRefund.getRefundNo())
                    .setAddTime(LocalDateTime.now())
                    .setTransactionId(transactionId)
                    .setRefundFee(refundFee)
                    .setRefundStatus(refundStatus.equals("0") ? OrderService.SUCCESS : OrderService.FAIL);
            if (null != refundRecvAccout) {
                report.setRefundRecvAccout(refundRecvAccout.asText());
            }
            if (null != refundTime) {
                report.setTimeEnd(DateUtil.strToLocalDateTime(refundTime.asText()));
            }
            orderRefundReportService.save(report);

            OrderBody orderBody = new OrderBody()
                    .setContent(jsonNodes.toPrettyString())
                    .setBid(orderRefund.getRid())
                    .setType(OrderBodyDTO.ORDER_TYPE_ORDER_REFUND_REPORT)
                    .setAddTime(report.getAddTime());
            orderBodyService.save(orderBody);
            JsonNode refundReasonNode = jsonNodes.get("refund_reason");
            String refundReason = Optional.ofNullable(refundReasonNode).map(JsonNode::asText).orElse("");
            saveRefundReportNotify(refundReason, report, orderRefund.getPayAmount());

            orderBody.freeData();

            orderRefund.freeData();
        } else {
            //处理过的不处理
            report.freeData();
        }
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
            @Override
            public void afterCompletion(int status) {
                if (TransactionSynchronization.STATUS_COMMITTED == status) {
                    log.info("提交成功，退款入账消息");
                    orderNotifyThread.awaken();
                }
            }
        });
    }

    /**
     * 保存到天府银行订单表中
     *
     * @param dto    前端创建订单参数
     * @param order  已创建订单参数
     * @param retMsg 调用支付接口以后的返回值
     */
    private void saveTfOrder(OrderDTO dto, Order order, JsonNode retMsg) {
        OrderTfDTO orderTf = new OrderTfDTO();
        orderTf.setOrderId(order.getOrderId());
        orderTf.setPartner(dto.getPartner());
        orderTf.setSubPartner(dto.getSubPartner());
        String transactionId = retMsg.get("transaction_id").asText();
        order.setTransactionId(transactionId);
        Optional.ofNullable(retMsg.get("pay_transno")).ifPresent(j -> orderTf.setPayTransno(j.asText()));
        Optional.ofNullable(retMsg.get("bank_type")).ifPresent(j -> orderTf.setBankType(j.asText()));
        Optional.ofNullable(retMsg.get("trans_channel")).ifPresent(j -> orderTf.setTransChannel(j.asText()));
        orderTfService.save(orderTf);
        orderTf.freeData();
    }

    /**
     * 退款通知
     *
     * @param orderReport 回执消息
     */
    @SneakyThrows
    private void saveRefundReportNotify(String refundReason, OrderRefundReport orderReport, BigDecimal payAmount) {
        Order order = orderService.getById(orderReport.getOrderId());
        String notifyUrl = order.getNotifyUrl();
        if (StringUtils.hasLength(notifyUrl)) {
            OrderReportNotify data = new OrderReportNotify();
            data.setOrderId(order.getOrderId());
            data.setAddTime(LocalDateTime.now());
            data.setOutTradeNo(order.getOutTradeNo());
            data.setThirdPartyOrder(order.getThirdPartyOrder());
            final ObjectNode jo = objectMapper.createObjectNode();
            jo.put("thirdPartyOrder", order.getThirdPartyOrder());
            jo.put("orderId", order.getOrderId());
            jo.put("state", 1);
            jo.put("resultCode", orderReport.getRefundStatus());
            jo.put("payAmount", payAmount);
            jo.put("timeEnd", orderReport.getTimeEnd().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")));
            jo.put("refundFee", orderReport.getRefundFee());
            jo.put("refundNo", orderReport.getRefundNo());
            jo.put("refundReason", refundReason);
            buildAttach(orderReport.getOrderId(), jo);
            data.setNotifyBody(jo.toString());
            data.setNotifyUrl(notifyUrl);
            orderReportNotifyService.save(data);
            data.freeData();
        }
    }

    /**
     * 支付通知
     *
     * @param orderReport 回执消息
     */
    @SneakyThrows
    private void saveReportNotify(OrderReport orderReport) {
        Order order = orderService.getById(orderReport.getOrderId());
        String notifyUrl = order.getNotifyUrl();
        if (StringUtils.hasLength(notifyUrl)) {
            OrderReportNotify data = new OrderReportNotify();
            data.setOrderId(order.getOrderId());
            data.setAddTime(orderReport.getAddTime());
            data.setOutTradeNo(order.getOutTradeNo());
            data.setThirdPartyOrder(order.getThirdPartyOrder());
            final ObjectNode jo = objectMapper.createObjectNode();
            jo.put("thirdPartyOrder", order.getThirdPartyOrder());
            jo.put("orderId", order.getOrderId());
            jo.put("state", 0);
            jo.put("resultCode", orderReport.getResultCode());
            jo.put("payAmount", order.getPayAmount());
            if (null != orderReport.getTimeEnd()) {
                jo.put("timeEnd", orderReport.getTimeEnd().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss")));
            }
            buildAttach(orderReport.getOrderId(), jo);
            data.setNotifyBody(jo.toString());
            data.setNotifyUrl(notifyUrl);
            orderReportNotifyService.save(data);
            data.freeData();
        }
    }

    @SneakyThrows
    private void buildAttach(String orderId, ObjectNode jo) {
        final String attach = orderExtendedService.getAttach(orderId);
        if (StringUtils.hasLength(attach)) {
            jo.set("attach", objectMapper.readTree(attach));
        }
    }

    /**
     * 查询订单状态
     *
     * @param partner    主商户号
     * @param subPartner 子商户号
     * @param outTradeNo 商户订单号/商户退款单号
     * @return map
     */
    @SneakyThrows
    public Map<String, Object> orderState(String partner, String subPartner, String outTradeNo) {
        final JsonNode originalMessage = orderStateJson(partner, subPartner, outTradeNo);
        return objectMapper.readValue(originalMessage.toString(), new TypeReference<>() {
        });

    }

    /**
     * 查询订单状态
     *
     * @param partner    主商户号
     * @param subPartner 子商户号
     * @param outTradeNo 商户订单号/商户退款单号
     * @return json
     */
    public JsonNode orderStateJson(String partner, String subPartner, String outTradeNo) {
        final BaseResponseBO baseResponseBO = orderApi.queryOrder(partner, subPartner, outTradeNo);
        return baseResponseBO.getOriginalMessage();
    }
}
